/**
 *  This file is part of MunchLevel.
 *
 *  MunchLevel is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  MunchLevel is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MunchLevel.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.munchlevel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.AsyncTask.Status;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.ext.R;
import android.view.ext.SatelliteMenu;
import android.view.ext.SatelliteMenu.SateliteClickedListener;
import android.view.ext.SatelliteMenuItem;
import android.widget.Toast;

import com.astuetz.viewpager.extensions.SwipeyTabsView;
import com.astuetz.viewpager.extensions.TabsAdapter;
import com.gandulf.guilib.util.Debug;
import com.munchlevel.data.GameInfo;
import com.munchlevel.data.PlayerInfo;
import com.munchlevel.fragment.PlayerFragment;
import com.munchlevel.util.BluetoothService;
import com.munchlevel.util.BluetoothService.BluetoothServiceListener;
import com.munchlevel.view.BasePlayerLayout;
import com.munchlevel.view.DiceView;
import com.munchlevel.view.adapter.MultiPlayerPagerAdapter;
import com.munchlevel.view.adapter.MultiSwipeyTabsAdapter;
import com.munchlevel.view.adapter.SwipeyTabsAdapter;
import com.munchlevel.view.listener.OnPlayerChangedListener;
import com.munchlevel.view.listener.ShakeListener;

public class GameActivity extends BaseGameActivity implements OnClickListener, OnPlayerChangedListener,
		OnPageChangeListener, BluetoothServiceListener, OnSharedPreferenceChangeListener {

	public static final String PLAYERS = "players";

	private static final int ITEM_SEX = 0;
	private static final int ITEM_CLASS = 1;
	private static final int ITEM_RACE = 2;
	private static final int ITEM_HALFBREED = 3;
	private static final int ITEM_SUPER_MUNCHKIN = 4;

	private DiceView ivDice6;

	private DeviceChecker mDeciveChecker;

	private SwipeyTabsView mSwipeyTabs;
	private TabsAdapter mSwipeyTabsAdapter;
	private ViewPager mPager;
	// private SwipeyTabsAdapter mSwipeyTabsAdapter;
	private MultiPlayerPagerAdapter mPagerAdapter;

	private ShakeListener mShaker;

	private SatelliteMenuItem genderItem;
	private SatelliteMenuItem superMunchkinItem;
	private SatelliteMenuItem halfbreedItem;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

		setContentView(R.layout.game);

		ivDice6 = (DiceView) findViewById(R.id.iv_dice);
		ivDice6.setOnClickListener(this);

		initViewPager(getGameInfo());

		initPieMenu();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * android.support.v4.view.ViewPager.OnPageChangeListener#onPageSelected
	 * (int)
	 */
	@Override
	public void onPageSelected(int position) {
		updatePieMenu();
		mSwipeyTabs.onPageSelected(position);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * android.support.v4.view.ViewPager.OnPageChangeListener#onPageScrolled
	 * (int, float, int)
	 */
	@Override
	public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
		mSwipeyTabs.onPageScrolled(position, positionOffset, positionOffsetPixels);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.support.v4.view.ViewPager.OnPageChangeListener#
	 * onPageScrollStateChanged(int)
	 */
	@Override
	public void onPageScrollStateChanged(int state) {
		mSwipeyTabs.onPageScrollStateChanged(state);
	}

	private void updatePieMenu() {
		if (getPlayerInfo().isMale())
			genderItem.setImgResourceId(R.drawable.male);
		else
			genderItem.setImgResourceId(R.drawable.female);

		if (getPlayerInfo().isHalfblood())
			halfbreedItem.setImgResourceId(R.drawable.halfblood_on);
		else
			halfbreedItem.setImgResourceId(R.drawable.halfblood_off);

		if (getPlayerInfo().isSuperMunchkin())
			superMunchkinItem.setImgResourceId(R.drawable.super_munchkin_on);
		else
			superMunchkinItem.setImgResourceId(R.drawable.super_munchkin_off);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.content.SharedPreferences.OnSharedPreferenceChangeListener#
	 * onSharedPreferenceChanged(android.content.SharedPreferences,
	 * java.lang.String)
	 */
	@Override
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

		if (key == null || MyPreferenceActivity.KEY_DICE.equals(key)) {
			if (sharedPreferences.getBoolean(MyPreferenceActivity.KEY_DICE, true)) {
				ivDice6.setVisibility(View.VISIBLE);
			} else {
				ivDice6.setVisibility(View.GONE);
			}
		}

		if (key == null || MyPreferenceActivity.KEY_SHAKE_DICE.equals(key)) {
			// -- shake dice
			boolean shake = sharedPreferences.getBoolean(MyPreferenceActivity.KEY_SHAKE_DICE, true);
			if (shake)
				registerShakeDice();
			else
				unregisterShakeDice();
		}
		if (key == null || MyPreferenceActivity.KEY_EDITIONS.equals(key)) {
			MunchLevelApplication.getInstance().getConfiguration().reload(sharedPreferences);
		}

		if (mPagerAdapter != null) {
			for (int i = 0; i < mPagerAdapter.getCount(); i++) {
				PlayerFragment playerLayout = mPagerAdapter.getItem(i);
				playerLayout.onSharedPreferenceChanged(sharedPreferences, key);
			}
		}

		super.onSharedPreferenceChanged(sharedPreferences, key);
	}

	/**
	 * 
	 */
	private void initPieMenu() {
		final SatelliteMenu menu = (SatelliteMenu) findViewById(R.id.menu);

		// Set from XML, possible to programmatically set
		// float distance =
		// TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 170,
		// getResources().getDisplayMetrics());
		// menu.setSatelliteDistance((int) distance);
		// menu.setExpandDuration(500);
		// menu.setCloseItemsOnClick(false);
		// menu.setTotalSpacingDegree(60);

		menu.setMainImage(R.drawable.munchkin);
		List<SatelliteMenuItem> items = new ArrayList<SatelliteMenuItem>();

		genderItem = new SatelliteMenuItem(ITEM_SEX, R.drawable.male);
		items.add(genderItem);

		superMunchkinItem = new SatelliteMenuItem(ITEM_SUPER_MUNCHKIN, R.drawable.super_munchkin_off);
		items.add(superMunchkinItem);

		items.add(new SatelliteMenuItem(ITEM_CLASS, R.drawable.clazz));

		halfbreedItem = new SatelliteMenuItem(ITEM_HALFBREED, R.drawable.halfblood_off);
		items.add(halfbreedItem);

		items.add(new SatelliteMenuItem(ITEM_RACE, R.drawable.race));
		// items.add(new SatelliteMenuItem(5, R.drawable.sat_item));
		menu.addItems(items);

		menu.setOnItemClickedListener(new SateliteClickedListener() {

			public void eventOccured(int id) {
				switch (id) {
				case ITEM_SEX:

					Debug.verbose("ITEM_GENDER clicked");

					getPlayerInfo().setMale(!getPlayerInfo().isMale());
					updatePieMenu();

					if (getPlayerFragment() != null)
						getPlayerFragment().notifyDataSetChanged();

					break;
				case ITEM_CLASS:
					openClassSelection();
					break;
				case ITEM_RACE:
					openRaceSelection();
					break;
				case ITEM_HALFBREED:
					getPlayerInfo().setHalfblood(!getPlayerInfo().isHalfblood());
					updateRaceString();
					updatePieMenu();
					break;
				case ITEM_SUPER_MUNCHKIN:
					getPlayerInfo().setSuperMunchkin(!getPlayerInfo().isSuperMunchkin());
					updateRaceString();
					updatePieMenu();
					break;
				}
			}

		});

		// if (preferences.getBoolean(MyPreferenceActivity.KEY_LEFT_HANDED,
		// false)) {
		// menuLayout.setStartDegree(90);
		// menuLayout.setEndDegree(180);
		// menuLayout.show(new Point(metrics.widthPixels,
		// metrics.heightPixels));
		// } else {
		// menuLayout.setStartDegree(0);
		// menuLayout.setEndDegree(90);
		// menuLayout.show(new Point(0, metrics.heightPixels));
		// }

	}

	private void updateRaceString() {
		if (getPlayerFragment() != null)
			getPlayerFragment().notifyDataSetChanged();
	}

	private void initViewPager(GameInfo gameInfo) {
		mPager = (ViewPager) findViewById(R.id.pager);

		if (gameInfo.getPlayers() > 1 && getScreenOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
			mPagerAdapter = new MultiPlayerPagerAdapter(this, 3, getSupportFragmentManager(), gameInfo);
			mSwipeyTabsAdapter = new MultiSwipeyTabsAdapter(this, gameInfo);
		} else {
			mPagerAdapter = new MultiPlayerPagerAdapter(this, 1, getSupportFragmentManager(), gameInfo);
			mSwipeyTabsAdapter = new SwipeyTabsAdapter(this, gameInfo);
		}

		mPagerAdapter.setOnPlayerChangedListener(this);
		mPager.setAdapter(mPagerAdapter);
		mPager.setCurrentItem(0);
		mPager.setPageMargin(1);

		mSwipeyTabs = (SwipeyTabsView) findViewById(R.id.tabs);
		mSwipeyTabs.setAdapter(mSwipeyTabsAdapter);
		// set onpageListener internally, but i need it too
		mSwipeyTabs.setViewPager(mPager);

		// since i overwrite the swipeytabs pagechanger i have to call the
		// listener methods from each method!!!
		mPager.setOnPageChangeListener(this);
	}

	@Override
	public void onGearChanged(BasePlayerLayout playerLayout, int oldVal, int newVal) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * android.support.v4.app.FragmentActivity#onConfigurationChanged(android
	 * .content.res.Configuration)
	 */
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);

		initViewPager(getGameInfo());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.munchlevel.view.listener.OnPlayerChangedListener#onGenderChanged(
	 * com.munchlevel.view.BasePlayerLayout, boolean, boolean)
	 */
	@Override
	public void onGenderChanged(PlayerInfo info, boolean oldVal, boolean newVal) {
		updatePieMenu();
	}

	@Override
	public void onLevelChanged(BasePlayerLayout playerLayout, int oldVal, int newVal) {
		if (newVal >= playerLayout.getMaximumLevel()) {
			startActivityForResult(new Intent(this, VictoryActivity.class), REQUEST_VICTORY);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.munchlevel.view.listener.OnPlayerChangedListener#onNameChanged(com
	 * .munchlevel.view.BasePlayerLayout, java.lang.String, java.lang.String)
	 */
	@Override
	public void onNameChanged(PlayerInfo info, String oldVal, String newVal) {
		mSwipeyTabs.notifyDatasetChanged();
	}

	protected PlayerFragment getPlayerFragment() {
		Fragment fragment = mPagerAdapter.getItem(mPager.getCurrentItem());

		if (fragment instanceof PlayerFragment) {
			return (PlayerFragment) fragment;
		} else
			return null;
	}

	private void unregisterShakeDice() {
		if (mShaker != null) {
			mShaker.setOnShakeListener(null);
			mShaker = null;
		}
	}

	private void registerShakeDice() {

		if (mShaker == null) {
			final Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
			try {
				mShaker = new ShakeListener(this);
				mShaker.setOnShakeListener(new ShakeListener.OnShakeListener() {
					public void onShake() {
						vibe.vibrate(100);
						ivDice6.rollDice6();
					}
				});
			} catch (UnsupportedOperationException e) {
				mShaker = null;
			}
		}
	}

	@Override
	public void onClick(View v) {
		if (v == ivDice6) {
			ivDice6.rollDice6();
		}
	}

	public boolean applyTheme() {
		boolean ok = true;

		for (int i = 0; i < mPagerAdapter.getCount(); i++) {
			PlayerFragment playerLayout = mPagerAdapter.getItem(i);
			ok &= playerLayout.applyTheme();
		}

		return ok;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.game_menu, menu);
		return true;
	}

	@Override
	protected void onResume() {
		if (mShaker != null)
			mShaker.resume();

		super.onResume();
	}

	@Override
	protected void onPause() {
		if (mShaker != null)
			mShaker.pause();

		super.onPause();
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {

		switch (item.getItemId()) {

		case R.id.scan:
			// Launch the DeviceListActivity to see devices and do scan
			Intent serverIntent = new Intent(GameActivity.this, DeviceListActivity.class);
			startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
			return true;

		case R.id.reset_game:
			getGameInfo().reset();

			mPagerAdapter.notifyDataSetChanged();

			return true;
		}

		return super.onOptionsItemSelected(item);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {

		switch (requestCode) {

		case REQUEST_CONNECT_DEVICE:
			// When DeviceListActivity returns with a device to connect
			if (resultCode == Activity.RESULT_OK) {
				// Get the device MAC address
				String connectAddress = data.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
				// Attempt to connect to the device
				Debug.verbose("Connecting with " + connectAddress);
				if (mDeciveChecker == null || mDeciveChecker.getStatus() == Status.FINISHED) {
					Debug.verbose("Starting new device checker for " + connectAddress);
					mDeciveChecker = new DeviceChecker();
					mDeciveChecker.execute(connectAddress);
				} else {
					Debug.verbose("Adding address to existing device checker " + connectAddress);
					mDeciveChecker.mCheckDevices.add(connectAddress);
				}
			}
			return;
		case REQUEST_ENABLE_BT:
			// When the request to enable Bluetooth returns
			if (resultCode == Activity.RESULT_OK) {
				Intent serverIntent = new Intent(GameActivity.this, DeviceListActivity.class);
				startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
			} else {
				// User did not enable Bluetooth or an error occured
				Debug.verbose("BT not enabled");
				Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
			}
			return;

		case REQUEST_VICTORY:
			if (resultCode == Activity.RESULT_OK) {
				finish();
			}
			return;
		case REQUEST_FIGHT:
			Debug.verbose("Transfering bonus infos and level");
			return;
		}

		super.onActivityResult(requestCode, resultCode, data);
	}

	@Override
	protected void initBluetoothService(BluetoothService service) {
		service.setBluetoothServiceListener(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.munchlevel.util.BluetoothService.BluetoothServiceListener#
	 * onCommandReceived(java.lang.String)
	 */
	@Override
	public boolean onCommandReceived(String cmd, String params) {
		if (cmd.equals(CMD_REQUEST_CONNECT_INFO)) {
			getBluetoothService().sendCommand(CMD_CONNECT_INFO, getGameInfo().getPlayer()[0].toString());
			return true;
		} else if (cmd.equals(CMD_REQUEST_ADD_INFO)) {
			for (PlayerInfo info : getGameInfo().getPlayer())
				getBluetoothService().sendCommand(CMD_ADD_INFO, info.toString());
			return true;
		} else if (cmd.equals(CMD_ADD_INFO)) {
			if (mDeciveChecker != null) {
				mDeciveChecker.addConfirmedDevice(getBluetoothService().getRemoteAddress());
			}
		}
		return false;
	}

	@Override
	public void onConnected(String deviceName, String address) {
		if (mDeciveChecker != null) {
			mDeciveChecker.addConnectedDevice(address);
		}
	}

	@Override
	public void onConnectFailed(String name, String address) {
		if (mDeciveChecker != null) {
			mDeciveChecker.addFailedDevice(address);
		}
	}

	protected PlayerInfo getPlayerInfo() {
		int index = mPager.getCurrentItem();
		return getGameInfo().getPlayer(index);
	}

	private void openClassSelection() {

		AlertDialog.Builder builder = new AlertDialog.Builder(this);

		List<String> classesList = new ArrayList<String>(MunchLevelApplication.getInstance().getConfiguration()
				.getClasses());
		classesList.add(0, getString(R.string.super_munchkin));

		final String[] classes = classesList.toArray(new String[0]);
		final boolean[] selection = new boolean[classes.length];
		selection[0] = getPlayerInfo().isSuperMunchkin();
		for (int i = 1; i < classes.length; i++) {
			if (getPlayerInfo().getClazz().contains(classes[i])) {
				selection[i] = true;
			} else {
				selection[i] = false;
			}
		}

		builder.setMultiChoiceItems(classes, selection, new DialogInterface.OnMultiChoiceClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which, boolean isChecked) {
				if (which == 0) {
					getPlayerInfo().setSuperMunchkin(isChecked);

				} else {
					if (isChecked) {
						getPlayerInfo().addClazz(classes[which]);
					} else {
						getPlayerInfo().removeClazz(classes[which]);
					}
				}

				AlertDialog alertDialog = (AlertDialog) dialog;

				for (int i = 1; i < classes.length; i++) {
					if (getPlayerInfo().getClazz().contains(classes[i])) {
						alertDialog.getListView().setItemChecked(i, true);
						selection[i] = true;
					} else {
						alertDialog.getListView().setItemChecked(i, false);
						selection[i] = false;
					}

				}
			}
		});

		builder.show().setOnDismissListener(new DialogInterface.OnDismissListener() {

			@Override
			public void onDismiss(DialogInterface dialog) {
				updateRaceString();
				updatePieMenu();
			}
		});
	}

	private void openRaceSelection() {

		AlertDialog.Builder builder = new AlertDialog.Builder(this);

		List<String> racesList = new ArrayList<String>(MunchLevelApplication.getInstance().getConfiguration()
				.getRaces());
		racesList.add(0, getString(R.string.halfblood));

		final String[] races = racesList.toArray(new String[0]);
		final boolean[] selection = new boolean[races.length];
		selection[0] = getPlayerInfo().isHalfblood();
		for (int i = 1; i < races.length; i++) {
			if (getPlayerInfo().getRace().contains(races[i])) {
				selection[i] = true;
			} else {
				selection[i] = false;
			}
		}

		builder.setMultiChoiceItems(races, selection, new DialogInterface.OnMultiChoiceClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which, boolean isChecked) {
				if (which == 0) {
					getPlayerInfo().setHalfblood(isChecked);
				} else {
					if (isChecked) {
						getPlayerInfo().addRace(races[which]);
					} else {
						getPlayerInfo().removeRace(races[which]);
					}
				}

				AlertDialog alertDialog = (AlertDialog) dialog;

				for (int i = 1; i < races.length; i++) {
					if (getPlayerInfo().getRace().contains(races[i])) {
						alertDialog.getListView().setItemChecked(i, true);
						selection[i] = true;
					} else {
						alertDialog.getListView().setItemChecked(i, false);
						selection[i] = false;
					}

				}

			}
		});

		builder.show().setOnDismissListener(new DialogInterface.OnDismissListener() {

			@Override
			public void onDismiss(DialogInterface dialog) {
				updateRaceString();
				updatePieMenu();
			}
		});
	}

	class DeviceChecker extends AsyncTask<String, Integer, Set<String>> {

		private static final int CONNECT_DELAY = 10000;
		private static final int RESPONSE_DELAY = 10000;

		protected Set<String> mCheckDevices;
		private Set<String> mConnectedDevices;
		private Set<String> mFailedDevices;
		private Set<String> mConfirmedDevices;

		/**
		 * 
		 */
		public DeviceChecker() {
			mCheckDevices = new HashSet<String>();
			mConfirmedDevices = new HashSet<String>();
			mFailedDevices = new HashSet<String>();
			mConnectedDevices = new HashSet<String>();
		}

		public synchronized void addConnectedDevice(String address) {
			mConnectedDevices.add(address);
			notify();
		}

		public synchronized void addConfirmedDevice(String address) {
			mConfirmedDevices.add(address);
			notify();
		}

		public synchronized void addFailedDevice(String address) {
			mFailedDevices.add(address);
			notify();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see android.os.AsyncTask#doInBackground(Params[])
		 */
		@Override
		protected synchronized Set<String> doInBackground(String... addresses) {
			int progress = 0;

			mCheckDevices.addAll(Arrays.asList(addresses));

			while (!mCheckDevices.isEmpty()) {

				String address = mCheckDevices.iterator().next();
				Debug.verbose("Checking " + address);

				getBluetoothService().connect(address);
				try {
					wait(CONNECT_DELAY);
				} catch (InterruptedException e) {
					Debug.verbose("Wakeup call connect");
				}

				if (mConnectedDevices.contains(address)) {
					getBluetoothService().sendCommand(CMD_REQUEST_ADD_INFO);

					try {
						wait(RESPONSE_DELAY);
					} catch (InterruptedException e) {
						Debug.verbose("Wakeup call message");
					}

					if (mConfirmedDevices.contains(address)) {
						Debug.verbose("Success adding device " + address + " checking next");
					} else {
						Debug.verbose("No response received from " + address);
						mFailedDevices.add(address);
					}

				} else {
					Debug.verbose("Couldn't connect with " + address);
					mFailedDevices.add(address);
				}

				mCheckDevices.remove(address);

				progress++;
				publishProgress(progress);
			}

			return mConfirmedDevices;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
		 */
		@Override
		protected void onPostExecute(Set<String> result) {
			if (result.isEmpty()) {
				Toast.makeText(GameActivity.this, R.string.not_connected, Toast.LENGTH_SHORT).show();
			} else {
				Toast.makeText(GameActivity.this, "Connected to " + result.size() + " device", Toast.LENGTH_LONG)
						.show();

				MunchLevelApplication.getInstance().confirmedDevices.addAll(result);
			}
			super.onPostExecute(result);
		}
	}

}